package com.yhos.platform.oopston.core.aop;

import com.fasterxml.jackson.databind.ObjectMapper;
import com.yhos.platform.oopston.core.base.PageRequest;
import com.yhos.platform.oopston.core.utils.JacksonUtils;
import javassist.*;
import javassist.bytecode.CodeAttribute;
import javassist.bytecode.LocalVariableAttribute;
import javassist.bytecode.MethodInfo;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.web.bind.annotation.RequestHeader;
import org.springframework.web.multipart.MultipartFile;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;

public class ParamsManager {

    private static Logger logger = LoggerFactory.getLogger(ParamsManager.class);
    //参数过滤掉的类型
    public static Class[] EXCLUDE_PARAMS = new Class[]{ HttpServletRequest.class, HttpServletResponse.class, ExtDefineParamsInfo.class, MultipartFile.class };
    //需要过滤掉的参数注解
    public static Class[] EXCLUDE_ANNOTATIONS = new Class[]{ RequestHeader.class};


    //JSON处理类
    private ObjectMapper objectMapper = JacksonUtils.getInstance();
    private static ParamsManager manager = null;
    private ClassPool classPool;

    {
        classPool = ClassPool.getDefault();
        classPool.insertClassPath(new ClassClassPath(getClass()));
    }

    private ParamsManager() {

    }

    public static synchronized ParamsManager getInstance() {
        if (manager == null) {
            manager = new ParamsManager();
        }
        return manager;
    }

    public Map<String, Object> getParamValueMap(Class clazz, Method method, Object[] args) {
        return getParamValueMap(clazz, method, args, null, null);
    }

    //根据类、方法、参数值返回值Map
    public Map<String, Object> getParamValueMap(Class clazz, Method method, Object[] args, Class[] excludeType, Class[] excludeAnnotations) {
        Set<String> excludeTypeSet = new HashSet<>();
        Set<Class> excludeAnnotationTypeSet = new HashSet<>();
        if (excludeType != null && excludeType.length > 0) {
            for (Class classType : excludeType) {
                excludeTypeSet.add(classType.getName());
            }
        }
        if (excludeAnnotations != null && excludeAnnotations.length > 0) {
            for (Class classType : excludeAnnotations) {
                excludeAnnotationTypeSet.add(classType);
            }
        }

        Map<String, Object> paramValueMap = new HashMap<>();
        try {
            CtClass ctClass = classPool.get(clazz.getName());
            CtClass[] parameterTypes = new CtClass[args.length];
            Class<?>[] parameterTypeList = method.getParameterTypes();
            for (int i = 0; i < parameterTypeList.length; i++) {
                parameterTypes[i] = classPool.get(parameterTypeList[i].getName());
            }
            Class<?>[] annotationTypes = new Class[args.length];
            for (int i = 0; i < method.getParameters().length; i++) {
                if (method.getParameters()[i].getAnnotations() != null
                    && method.getParameters()[i].getAnnotations() != null
                    && method.getParameters()[i].getAnnotations().length > 0) {
                    annotationTypes[i] = method.getParameters()[i].getAnnotations()[0].annotationType();
                } else {
                    annotationTypes[i] = null;
                }
            }
            CtMethod ctMethod = ctClass.getDeclaredMethod(method.getName(), parameterTypes);
            MethodInfo methodInfo = ctMethod.getMethodInfo();
            CodeAttribute codeAttribute = methodInfo.getCodeAttribute();
            LocalVariableAttribute attr = (LocalVariableAttribute) codeAttribute.getAttribute(LocalVariableAttribute.tag);
            if (attr == null) {
                logger.warn("can not found localVariableAttribute, methodName:" + method.getName());
                return null;
            }

            int pos = Modifier.isStatic(ctMethod.getModifiers()) ? 0 : 1;
            for (int i = 0; i < args.length; i++) {
                Class<?> paramsTypeClass = parameterTypeList[i];
                if (excludeTypeSet.contains(paramsTypeClass.getName()) || excludeAnnotationTypeSet.contains(annotationTypes[i])) {
                    continue;
                }

                //为空或者是基础类型就直接添加
                if (args[i] == null || isWrapClass(paramsTypeClass)) {
                    String variableName = attr.variableName(i + pos);
                    if (StringUtils.isNotBlank(variableName)) {
                        paramValueMap.put(variableName, args[i]);
                    }
                } else {
                    //修正LIST类型参数接异常问题
                    if (paramsTypeClass.isArray()) {
                        continue;
                    }
                    try {
                        String jsonStr = "{";
                        Object obj = args[i];
                        Class objClass = obj.getClass();
                        Field[] fields = objClass.getDeclaredFields();
                        if(fields != null){
                            for(Field f: fields){
                                Object value = null;
                                if (excludeTypeSet.contains(f.getType().getName())) {
                                } else {
                                    f.setAccessible(true);
                                    try {
                                        value = f.get(obj);
                                    } catch (IllegalAccessException e) {
                                        e.printStackTrace();
                                    }
                                }
                                jsonStr += "\"" + f.getName() + "\"";
                                if(value != null){
                                    jsonStr += ":" + objectMapper.writeValueAsString(value) + ",";
                                } else {
                                    jsonStr += ":null,";
                                }
                            }
                        }
                        if (objClass.getSuperclass() != null
                                && objClass.getSuperclass().getSimpleName().equalsIgnoreCase(PageRequest.class.getSimpleName())) {
                            Field[] superClassFields = objClass.getSuperclass().getDeclaredFields();
                            if(superClassFields != null){
                                for(Field f: superClassFields){
                                    Object value = null;
                                    if (excludeTypeSet.contains(f.getType().getName())) {
                                    } else {
                                        f.setAccessible(true);
                                        try {
                                            value = f.get(obj);
                                        } catch (IllegalAccessException e) {
                                            e.printStackTrace();
                                        }
                                    }
                                    jsonStr += "\"" + f.getName() + "\"";
                                    if(value != null){
                                        jsonStr += ":" + objectMapper.writeValueAsString(value) + ",";
                                    } else {
                                        jsonStr += ":null,";
                                    }
                                }
                            }
                        }
                        String json = jsonStr.substring(0, jsonStr.lastIndexOf(",")) + "}";
                        paramValueMap.putAll(objectMapper.readValue(json, Map.class));
                    } catch (IOException e) {
                        logger.error("ParamsManager getParamValueMap json error." + e.getMessage(), e);
                    }
                }
            }
        } catch (Exception e) {
            logger.error("ParamsManager getParamValueMap error, methodName:" + method.getName(), e);
        }
        return paramValueMap;
    }

    public Boolean isWrapClass(Class clz) {
        Boolean isWrapClass;
        try {
            isWrapClass = ((Class) clz.getField("TYPE").get(null)).isPrimitive();
        } catch (Exception e) {
            isWrapClass = false;
        }

        return isWrapClass || String.class.getName().equals(clz.getName());
    }

}
